#include "ZipUtil.h"
#include "zip/unzip.h"
#include "StringUtil.h"

namespace OCPP
{
    void ZipUtil::unzip(String fileName, String path)
    {
        unzFile zFile = unzOpen(fileName.c_str());

        if (zFile)
        {
            if (path == "")
            {
                String baseName;
                StringUtil::splitFilename(fileName, baseName, path);
            }
            path += "/";

            int status = unzGoToFirstFile(zFile);
            while (UNZ_OK == status)
            {
                if (UNZ_OK == unzOpenCurrentFile(zFile))
                {
                    char buffer[1024];
                    unz_file_info info;
                    unzGetCurrentFileInfo(zFile, &info, buffer, sizeof(buffer), NULL, 0, NULL, 0);
                    String entryFileName = path + buffer;
                    std::ofstream ofs(entryFileName, std::ios::binary);
                    if (ofs)
                    {
                        int len = unzReadCurrentFile(zFile, buffer, sizeof(buffer));
                        while (len > 0)
                        {
                            ofs.write(buffer, len);
                            len = unzReadCurrentFile(zFile, buffer, sizeof(buffer));
                        }
                        ofs.close();
                    }

                    unzCloseCurrentFile(zFile);
                }

                status = unzGoToNextFile(zFile);
            }

            unzClose(zFile);
        }
    }

    String ZipUtil::gzipCompress(const String& data)
    {
        return gzipCompress(data.c_str(), data.size());
    }

#define CHUNK 32768
    bool ZipUtil::compress(std::ostream& fout, const String& src)
    {
        int ret, flush = Z_FINISH;
        unsigned have;
        z_stream strm;
        unsigned char out[CHUNK];

        int level = 8;
        int stategy = Z_DEFAULT_STRATEGY;

        /* allocate deflate state */
        strm.zalloc = Z_NULL;
        strm.zfree = Z_NULL;
        strm.opaque = Z_NULL;
        ret = deflateInit2(&strm, level, Z_DEFLATED,
            -15,//15+16, // +16 to use gzip encoding
            8, // default
            stategy);
        if (ret != Z_OK) return false;

        strm.avail_in = (uInt)src.size();
        strm.next_in = (Bytef*)(&(*src.begin()));

        /* run deflate() on input until output buffer not full, finish
           compression if all of source has been read in */
        do
        {
            strm.avail_out = CHUNK;
            strm.next_out = out;
            ret = deflate(&strm, flush);    /* no bad return value */

            if (ret == Z_STREAM_ERROR)
            {
                return false;
            }

            have = CHUNK - strm.avail_out;
            if (have > 0) fout.write((const char*)out, have);

            if (fout.fail())
            {
                (void)deflateEnd(&strm);
                return false;
            }
        } while (strm.avail_out == 0);

        /* clean up and return */
        (void)deflateEnd(&strm);
        return true;
    }

    /* Compress gzip data */
    String ZipUtil::gzipCompress(const char* data, const size_t ndata)
    {
        z_stream strm = { nullptr,
                            0,
                            0,
                            nullptr,
                            0,
                            0,
                            nullptr,
                            nullptr,
                            nullptr,
                            nullptr,
                            nullptr,
                            0,
                            0,
                            0 };
        if (data && ndata > 0)
        {
            if (deflateInit2(&strm,
                Z_DEFAULT_COMPRESSION,
                Z_DEFLATED,
                MAX_WBITS + 16,
                8,
                Z_DEFAULT_STRATEGY) != Z_OK)
            {
                return String{};
            }
            String outstr;
            outstr.resize(compressBound(static_cast<uLong>(ndata)));
            strm.next_in = (Bytef*)data;
            strm.avail_in = static_cast<uInt>(ndata);
            int ret;
            do
            {
                if (strm.total_out >= outstr.size())
                {
                    outstr.resize(strm.total_out * 2);
                }
                assert(outstr.size() >= strm.total_out);
                strm.avail_out = static_cast<uInt>(outstr.size() - strm.total_out);
                strm.next_out = (Bytef*)outstr.data() + strm.total_out;
                ret = deflate(&strm, Z_FINISH); /* no bad return value */
                if (ret == Z_STREAM_ERROR)
                {
                    (void)deflateEnd(&strm);
                    return String{};
                }
            } while (strm.avail_out == 0);
            assert(strm.avail_in == 0);
            assert(ret == Z_STREAM_END); /* stream will be complete */
            outstr.resize(strm.total_out);
            /* clean up and return */
            (void)deflateEnd(&strm);
            return outstr;
        }
        return String{};
    }

    String ZipUtil::gzipDecompress(const String& data)
    {
        return gzipDecompress(data.c_str(), data.size());
    }

    String ZipUtil::gzipDecompress(const char* data, const size_t ndata)
    {
        if (ndata == 0)
            return String(data, ndata);

        auto full_length = ndata;

        auto decompressed = String(full_length * 2, 0);
        bool done = false;

        z_stream strm = { nullptr,
                         0,
                         0,
                         nullptr,
                         0,
                         0,
                         nullptr,
                         nullptr,
                         nullptr,
                         nullptr,
                         nullptr,
                         0,
                         0,
                         0 };
        strm.next_in = (Bytef*)data;
        strm.avail_in = static_cast<uInt>(ndata);
        strm.total_out = 0;
        strm.zalloc = Z_NULL;
        strm.zfree = Z_NULL;
        if (inflateInit2(&strm, (15 + 32)) != Z_OK)
        {
            return String{};
        }
        while (!done)
        {
            // Make sure we have enough room and reset the lengths.
            if (strm.total_out >= decompressed.length())
            {
                decompressed.resize(decompressed.length() * 2);
            }
            strm.next_out = (Bytef*)decompressed.data() + strm.total_out;
            strm.avail_out =
                static_cast<uInt>(decompressed.length() - strm.total_out);
            // Inflate another chunk.
            int status = inflate(&strm, Z_SYNC_FLUSH);
            if (status == Z_STREAM_END)
            {
                done = true;
            }
            else if (status != Z_OK)
            {
                break;
            }
        }
        if (inflateEnd(&strm) != Z_OK)
            return String{};
        // Set real length.
        if (done)
        {
            decompressed.resize(strm.total_out);
            return decompressed;
        }
        else
        {
            return String{};
        }
    }

}
